use crate::{
    db::models::{Contract, Project, Provider},
    schema::{
        contract::{self, dsl::*},
        project, provider,
    },
};
use actix_web::{
    post,
    web::{self, Data, Json},
    HttpResponse,
};
use diesel::{
    dsl::{exists, sql},
    insert_into, select,
    sql_types::{Integer, VarChar},
    update,
};
use diesel::{prelude::*, sql_types::Bool};

use crate::{api::Page, DbPool};

#[derive(Debug, serde::Deserialize)]
pub struct PageContractReq {
    page: i64,
    size: i64,
    keyword: String,
    project_id: i32,
    provider_id: i32,
}

const SUB_CONTRACT: i32 = 1;
const MAIN_CONTRACT: i32 = 0;

#[derive(Debug, serde::Serialize, Queryable)]
pub struct ContractVo {
    pub id: i32,
    pub project_id: i32,
    pub provider_id: i32,
    pub no: String,
    pub sub_contract: i32,
    pub parent_no: Option<String>,
    pub contract_date: Option<i64>,
    pub project_name: String,
    pub provider_name: String,
    pub contract_amount: f64,
    pub remarks: Option<String>,
}

const SIZE_MAX: i64 = 50000000000;
const SIZE_INVERSE: i64 = -1;

#[post("/page")]
async fn page_contract(
    db: Data<DbPool>,
    request: web::Json<PageContractReq>,
) -> Json<Page<ContractVo>> {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();
        let mut size = req.size;
        if req.size.eq(&SIZE_INVERSE) {
            size = SIZE_MAX;
        }
        let data = contract::table
            .inner_join(project::table)
            .inner_join(provider::table)
            .filter(
                sql::<Bool>("no like ")
                    .bind::<VarChar, _>(format!("%{}%", req.keyword))
                    .sql(" and (project_id = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(") and ( provider_id = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(") "),
            )
            .limit(size)
            .offset((req.page - 1) * size)
            .select((
                contract::id,
                contract::project_id,
                contract::provider_id,
                contract::no,
                contract::sub_contract,
                contract::parent_no,
                contract::contract_date,
                project::name,
                provider::name,
                contract::contract_amount,
                contract::remarks,
            ))
            .load::<ContractVo>(&mut conn);
        let c: Result<i64, _> = contract
            .select(diesel::dsl::count(contract::id))
            .filter(
                sql::<Bool>("no like ")
                    .bind::<VarChar, _>(format!("%{}%", req.keyword))
                    .sql(" and (project_id = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.project_id)
                    .sql(") and ( provider_id = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(" or -1 = ")
                    .bind::<Integer, _>(req.provider_id)
                    .sql(") "),
            )
            .first(&mut conn);
        match data {
            Ok(d) => Page {
                total: c.unwrap(),
                content: d,
            },
            Err(e) => {
                println!("{:?}", e);
                return Page {
                    total: 0,
                    content: vec![],
                };
            }
        }
    })
    .await;
    Json(data.unwrap())
}

#[derive(Debug, serde::Deserialize)]
pub struct CreateContractVo {
    project_id: i32,
    provider_id: i32,
    no: String,
    parent_no: Option<String>,
    contract_date: Option<i64>,
    contract_amount: f64,
    remarks: Option<String>,
}

#[post("/create")]
async fn create_contract(db: Data<DbPool>, request: web::Json<CreateContractVo>) -> HttpResponse {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();
        // 判断项目是否存在
        let the_project: Result<Project, _> = project::dsl::project
            .filter(project::id.eq(&req.project_id))
            .first::<Project>(&mut conn);
        if let Err(_) = the_project {
            return 1;
        }

        // 判断供应商是否存在
        let the_provider: Result<Provider, _> = provider::dsl::provider
            .filter(provider::id.eq(&req.provider_id))
            .first::<Provider>(&mut conn);
        if let Err(_) = the_provider {
            return 2;
        }

        // 判断主合同是否存在
        if let Some(pn) = req.parent_no.clone() {
            let exists: Result<bool, diesel::result::Error> =
                select(exists(contract.filter(contract::no.eq(&pn)))).get_result(&mut conn);
            if let Ok(s) = exists {
                if !s {
                    return 3;
                }
            }
        }

        let exists: Result<bool, diesel::result::Error> =
            select(exists(contract.filter(contract::no.eq(&req.no)))).get_result(&mut conn);
        if let Ok(s) = exists {
            if s {
                return 0;
            }
        }

        let contract_to_save = Contract {
            id: 0,
            project_id: req.project_id,
            provider_id: req.provider_id,
            no: req.no,
            sub_contract: get_sub_contract_flag(req.parent_no.clone()),
            parent_no: req.parent_no,
            contract_date: req.contract_date,
            contract_amount: req.contract_amount,
            remarks: req.remarks,
        };
        let result = insert_into(contract)
            .values(&contract_to_save)
            .execute(&mut conn);
        match result {
            Ok(_) => 200,
            _ => 0,
        }
    })
    .await;
    match data.unwrap() {
        0 => HttpResponse::BadRequest().body("合同已存在"),
        1 => HttpResponse::BadRequest().body("项目不存在"),
        2 => HttpResponse::BadRequest().body("供应商不存在"),
        3 => HttpResponse::BadRequest().body("主合同不存在"),
        _ => HttpResponse::Ok().json(true),
    }
}

fn get_sub_contract_flag(pn: Option<String>) -> i32 {
    if let Some(_) = pn {
        return SUB_CONTRACT;
    }
    MAIN_CONTRACT
}

#[derive(Debug, serde::Deserialize)]
pub struct UpdateContractVo {
    id: i32,
    project_id: i32,
    provider_id: i32,
    no: String,
    parent_no: Option<String>,
    contract_date: Option<i64>,
    contract_amount: f64,
    remarks: Option<String>,
}

#[post("/update")]
async fn update_contract(db: Data<DbPool>, request: web::Json<UpdateContractVo>) -> HttpResponse {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();

        // 判断项目是否存在
        let the_project: Result<Project, _> = project::dsl::project
            .filter(project::id.eq(&req.project_id))
            .first::<Project>(&mut conn);
        if let Err(_) = the_project {
            return 1;
        }

        // 判断供应商是否存在
        let the_provider: Result<Provider, _> = provider::dsl::provider
            .filter(provider::id.eq(&req.provider_id))
            .first::<Provider>(&mut conn);
        if let Err(_) = the_provider {
            return 2;
        }

        // 判断主合同是否存在
        if let Some(pn) = req.parent_no.clone() {
            let exists: Result<bool, diesel::result::Error> =
                select(exists(contract.filter(contract::no.eq(&pn)))).get_result(&mut conn);
            if let Ok(s) = exists {
                if !s {
                    return 3;
                }
            }
        }

        let exists: Result<bool, diesel::result::Error> = select(exists(
            contract.filter(contract::no.eq(&req.no).and(contract::id.ne(&req.id))),
        ))
        .get_result(&mut conn);
        if let Ok(s) = exists {
            if s {
                return 0;
            }
        }

        let result: Result<usize, _> = update(contract.filter(contract::id.eq(req.id)))
            .set((
                contract::project_id.eq(req.project_id),
                contract::provider_id.eq(req.provider_id),
                contract::no.eq(req.no.clone()),
                contract::sub_contract.eq(get_sub_contract_flag(req.parent_no.clone())),
                contract::contract_date.eq(req.contract_date),
                contract::contract_amount.eq(req.contract_amount),
                contract::remarks.eq(req.remarks),
            ))
            .execute(&mut conn);
        match result {
            Ok(_) => 200,
            _ => 0,
        }
    })
    .await;
    match data.unwrap() {
        0 => HttpResponse::BadRequest().body("合同已存在"),
        1 => HttpResponse::BadRequest().body("项目不存在"),
        2 => HttpResponse::BadRequest().body("供应商不存在"),
        3 => HttpResponse::BadRequest().body("主合同不存在"),
        _ => HttpResponse::Ok().json(true),
    }
}

#[derive(Debug, serde::Deserialize)]
pub struct DeleteContractReq {
    id: i32,
}

#[post("/delete")]
async fn delete_contract(db: Data<DbPool>, request: web::Json<DeleteContractReq>) -> HttpResponse {
    let req = request.0;
    let data = web::block(move || {
        let mut conn = db.get().unwrap();
        let result = diesel::delete(contract.filter(contract::id.eq(req.id))).execute(&mut conn);
        match result {
            Ok(d) => d,
            _ => 0,
        }
    })
    .await;
    match data.unwrap() {
        0 => HttpResponse::BadRequest().body("未找到记录"),
        _ => HttpResponse::Ok().json(true),
    }
}
